package com.barnaby.sudoku;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Paint.FontMetrics;
import android.graphics.Paint.Style;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AnimationUtils;

public class BoardView extends View {
	private static final String TAG = "Sudoku";
	private final GameActivity gameActivity;
	
	private final Paint background;
	private final Paint dark;
	private final Paint hilite;
	private final Paint light;
	private Paint foreground;
	private FontMetrics fm;
	private final Paint hint;
	private CanvasSubsection boardCanvas;
	
	public class CanvasSubsection{
		private Canvas canvas;
		private final View view;
		private float anchorX;
		private float anchorY;
		private float width;
		private float height;
		private Rect rect;
		
		public CanvasSubsection(View view, Canvas canvas, float anchorX, float anchorY, float width, float height){
			this.setCanvas(canvas);
			this.view = view;
			this.setAnchorX(anchorX);
			this.setAnchorY(anchorY);
			this.setWidth(width);
			this.setHeight(height);
			this.rect = new Rect();
			
		}
		
		public float getWidth(){
			return this.width;
		}
		
		public boolean pointInCanvas(float x, float y){
			return (x > this.getAnchorX() && x < this.getAnchorX() + this.getWidth()
					&& y > this.getAnchorY() && y < this.getAnchorY() + this.getHeight());
		}
		
		public float xToInternal(float x){
			return x - getAnchorX();
		}
		
		public float yToInternal(float y){
			return y - getAnchorY();
		}

		private float xToExternal(float x){
			float adjusted = x + getAnchorX();
			adjusted = adjusted > getAnchorX() + getWidth() ? getAnchorX() + getWidth() : adjusted;
			adjusted = adjusted < getAnchorX() ? getAnchorX() : adjusted;
			return adjusted;
		}
		
		private float yToExternal(float y){
			float adjusted = y + getAnchorY();
			adjusted = adjusted > getAnchorY() + getHeight() ? getAnchorY() + getHeight() : adjusted;
			adjusted = adjusted < getAnchorY() ? getAnchorY() : adjusted;
			return adjusted;
		}
		
		public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) {
			if (getCanvas() != null)
				getCanvas().drawLine(xToExternal(startX), yToExternal(startY), xToExternal(stopX), yToExternal(stopY), paint);
		}

		public void drawText(String text, float x, float y, Paint paint){
			getCanvas().drawText(text, xToExternal(x), yToExternal(y), paint);
		}
		
		public void drawRect(Rect rect, Paint paint){
			
			this.rect.left = rect.left + (int)getAnchorX();
			this.rect.top = rect.top + (int)getAnchorY();
			this.rect.right = rect.right + (int)getAnchorX();
			this.rect.bottom = rect.bottom + (int)getAnchorY();
			getCanvas().drawRect(this.rect, paint);
			getView().invalidate(this.rect);
		}
		
		public Canvas getCanvas() {
			return canvas;
		}

		public void setCanvas(Canvas canvas) {
			this.canvas = canvas;
		}

		public float getAnchorX() {
			return anchorX;
		}

		public void setAnchorX(float anchorX) {
			this.anchorX = anchorX;
		}

		public float getAnchorY() {
			return anchorY;
		}

		public void setAnchorY(float anchorY) {
			this.anchorY = anchorY;
		}

		public void setWidth(float width) {
			this.width = width;
		}

		public float getHeight() {
			return height;
		}

		public void setHeight(float hieght) {
			this.height = hieght;
		}

		private View getView() {
			return view;
		}

	}
	
	
	public BoardView(Context context) {
		super(context);
		this.gameActivity = (GameActivity) context;
		setFocusable(true);
		setFocusableInTouchMode(true);
		
		background = new Paint();
		background.setColor(getResources().getColor(
		R.color.puzzle_background));
		
		dark = new Paint();
		dark.setColor(getResources().getColor(R.color.puzzle_dark));
		hilite = new Paint();
		hilite.setColor(getResources().getColor(R.color.puzzle_hilite));
		light = new Paint();
		light.setColor(getResources().getColor(R.color.puzzle_light));
		hint = new Paint();
		foreground = new Paint(Paint.ANTI_ALIAS_FLAG);
		foreground.setColor(getResources().getColor(R.color.puzzle_foreground));
		foreground.setTextAlign(Paint.Align.CENTER);
		foreground.setStyle(Style.FILL);
		
		boardCanvas = new CanvasSubsection(this, null, 0, 0, 0, 0);
	}
	
	private float cellWidth; // width of one tile
	private float cellHeight; // height of one tile
	private int selX; // X index of selection
	private int selY; // Y index of selection
	private final Rect selRect = new Rect();
	
	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		boardCanvas.setAnchorX(getWidth() / 20);
		boardCanvas.setAnchorY(getWidth() / 20);
		boardCanvas.setWidth(getWidth() - 2 * boardCanvas.getAnchorX());
		boardCanvas.setHeight(getHeight()  * 4/5 - 2 * boardCanvas.getAnchorX());
		
		cellWidth =(boardCanvas.getWidth()/9);
		cellHeight = (boardCanvas.getHeight()/9);
		getRect(selX, selY, selRect);
		
		foreground.setTextSize(cellHeight * 0.75f);
		foreground.setTextScaleX(cellWidth / cellHeight);

		fm = foreground.getFontMetrics();
		
		Log.d(TAG, "onSizeChanged: width " + cellWidth + ", height "
		+ cellHeight);
		super.onSizeChanged(w, h, oldw, oldh);
	}
	
	private void getRect(int x, int y, Rect rect) 
	{
		rect.set((int) (x * cellWidth), (int) (y * cellHeight), (int) (x * cellWidth + cellWidth), (int) (y * cellHeight + cellHeight));
	}
	
	@Override
	protected void onDraw(Canvas canvas) {
		boardCanvas.setCanvas(canvas);
		
		drawBackground(canvas, background);
		drawBoard(boardCanvas, dark, hilite, light);
		drawNumbers(boardCanvas);
		drawHints(boardCanvas, hint);
		highlightSelectedCell(boardCanvas);
	}

	private void highlightSelectedCell(CanvasSubsection canvas) {
		// Draw the selection...
		Log.d(TAG, "selRect=" + selRect);
		Paint selected = new Paint();
		selected.setColor(getResources().getColor(R.color.puzzle_selected));
		canvas.drawRect(selRect, selected);
	}

	private void drawNumbers(CanvasSubsection canvas) {
		float x = cellWidth / 2;
		// Centering in Y: measure ascent/descent first
		float y = cellHeight / 2 - (fm.ascent + fm.descent) / 2;
		for (int i = 0; i < 9; i++) {
			for (int j = 0; j < 9; j++) {
				canvas.drawText(this.gameActivity.getTileString(i, j), i * cellWidth + x, j * cellHeight + y, foreground);
			}
		}
	}

	private void drawHints(CanvasSubsection canvas, Paint hint) {
		int c[] = { getResources().getColor(R.color.puzzle_hint_0),
					getResources().getColor(R.color.puzzle_hint_1),
					getResources().getColor(R.color.puzzle_hint_2), };
		Rect r = new Rect();
		for (int i = 0; i < 9; i++) {
			for (int j = 0; j < 9; j++) {
				int movesleft = gameActivity.getNumberOfPossibleValues(i, j) - 2;
				if (movesleft < c.length && gameActivity.getTileString(i, j) == "") {
					getRect(i, j, r);
					hint.setColor(c[movesleft]);
					canvas.drawRect(r, hint);
				}
			}
		}
	}

	private void drawBoard(CanvasSubsection canvas, Paint dark, Paint hilite, Paint light) {
		// Draw the minor grid lines
		for (int i = 0; i < 9; i++) {
			canvas.drawLine(0, i * cellHeight, canvas.getWidth(), i * cellHeight, light);
			canvas.drawLine(0, i * cellHeight + 1, canvas.getWidth(), i * cellHeight + 1, hilite);
			canvas.drawLine(i * cellWidth, 0, i * cellWidth, canvas.getHeight(), light);
			canvas.drawLine(i * cellWidth + 1, 0, i * cellWidth + 1, canvas.getHeight(), hilite);
		}
		// Draw the major grid lines
		for (int i = 0; i < 9; i++) {
			if (i % 3 != 0)
				continue;
			canvas.drawLine(0, i * cellHeight + 1,  canvas.getWidth(), i * cellHeight + 1, hilite);
			canvas.drawLine(0, i * cellHeight,  canvas.getWidth(), i * cellHeight, dark);
			canvas.drawLine(i * cellWidth + 1, 0, i * cellWidth + 1,  canvas.getHeight(), hilite);
			canvas.drawLine(i * cellWidth, 0, i * cellWidth,  canvas.getHeight(), dark);
		}
	}

	private void drawBackground(Canvas canvas, Paint background) {
		canvas.drawRect(0, 0, getWidth(), getHeight(), background);
	}
	
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		Log.d(TAG, "onKeyDown: keycode=" + keyCode + ", event="+ event);
		switch (keyCode) {
			case KeyEvent.KEYCODE_DPAD_UP: select(selX, selY - 1); break;
			case KeyEvent.KEYCODE_DPAD_DOWN: select(selX, selY + 1); break;
			case KeyEvent.KEYCODE_DPAD_LEFT: select(selX - 1, selY); break;
			case KeyEvent.KEYCODE_DPAD_RIGHT: select(selX + 1, selY); break;
			case KeyEvent.KEYCODE_0:
			case KeyEvent.KEYCODE_SPACE: setSelectedTile(0); break;
			case KeyEvent.KEYCODE_1: setSelectedTile(1); break;
			case KeyEvent.KEYCODE_2: setSelectedTile(2); break;
			case KeyEvent.KEYCODE_3: setSelectedTile(3); break;
			case KeyEvent.KEYCODE_4: setSelectedTile(4); break;
			case KeyEvent.KEYCODE_5: setSelectedTile(5); break;
			case KeyEvent.KEYCODE_6: setSelectedTile(6); break;
			case KeyEvent.KEYCODE_7: setSelectedTile(7); break;
			case KeyEvent.KEYCODE_8: setSelectedTile(8); break;
			case KeyEvent.KEYCODE_9: setSelectedTile(9); break;
			case KeyEvent.KEYCODE_ENTER:
			case KeyEvent.KEYCODE_DPAD_CENTER: gameActivity.showKeypadOrError(selX, selY); 
				break;
			default: return super.onKeyDown(keyCode, event);
		}
		return true;
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if (event.getAction() != MotionEvent.ACTION_DOWN)
			return super.onTouchEvent(event);
		
		if (boardCanvas.pointInCanvas(event.getX(), event.getY())){
			select((int) (boardCanvas.xToInternal(event.getX()) / cellWidth), 
					(int) (boardCanvas.yToInternal(event.getY()) / cellHeight));
			gameActivity.showKeypadOrError(selX, selY);
		}
		
		Log.d(TAG, "onTouchEvent: x " + selX + ", y " + selY);
		return true;
	}
		
	public void setSelectedTile(int tile) {
		if (gameActivity.setTile(selX, selY, tile)) {
			invalidate();// may change hints
		} else {
		// Number is not valid for this tile
			Log.d(TAG, "setSelectedTile: invalid: " + tile);
		}
		}
	
	private void select(int x, int y) {
		invalidate(selRect);
		selX = Math.min(Math.max(x, 0), 8);
		selY = Math.min(Math.max(y, 0), 8);
		getRect(selX, selY, selRect);
		//invalidate();
	}
	
	
}